#pragma once

#ifdef _WIN32
#include <winsock2.h>
#include <Inaddr.h>
#include <In6addr.h>
#include <Ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#define SOCKET int
#endif
#include <time.h>
#include <stdint.h>
#include <unordered_set>

namespace cybertron
{
	class union_address {
	public:
		union_address(bool isV4 = true);
		union_address(uint32_t addr4 /* network order */);
		union_address(uint8_t addr6[16] /* network order */, uint32_t scope = 0 /* host order */);
		union_address(const union_address& a);

		bool is_v4() const { return _v4; }
		const in_addr& v4_addr() { return _addr.v4; }
		const in6_addr& v6_addr() { return _addr.v6; }
		uint32_t get_scope() { return _scopeid; }
		std::string to_string();

		union_address& operator=(const union_address& a);
		bool operator==(const union_address& a) const;
		bool operator!=(const union_address& a) const;
		bool operator<(const union_address& a) const;

	private:
		bool _v4;

		union addr_t {
			in_addr v4;
			in6_addr v6;
		} _addr;

		uint32_t _scopeid;
	};

	class union_endpoint {
	public:
		union_endpoint(bool tcp = true, bool isV4 = true);
		union_endpoint(const char* ip, uint16_t port, bool tcp = true, bool isV4 = true);
		union_endpoint(const union_endpoint& ep);
		~union_endpoint();

		bool operator<(const union_endpoint& ep) const;
		void set_tcp(bool tcp) { _tcp = tcp; }

		int from_socket_addr(const sockaddr* a, int len);
		const sockaddr* socket_addr();
		int socket_addr_len();

		bool is_v4() const { return _addr.is_v4(); }
		bool is_tcp() const { return _tcp; }
		uint16_t get_port() const { return _port; }
		union_address& get_addr() { return _addr; }

		const char *to_string();

	private:
		union_address _addr;
		uint16_t _port; /* host order */
		bool _tcp;

		sockaddr *_soaddr;
	};

	class SocketHelper
	{
	public:
		static void init();
		static void destroy();
		static SOCKET socket(const union_endpoint& ep);

		static int set_reuse(SOCKET sockfd, bool enable);
		static int set_v6only(SOCKET sockfd, bool enable);
		static int set_blocking(SOCKET sockfd, bool enable);

		static int bind(SOCKET sockfd, union_endpoint& ep);
		static int listen(SOCKET listenfd, int backlog);
		static SOCKET accept(SOCKET listenfd, union_endpoint& ep);
		static int connect(SOCKET sockfd, union_endpoint& ep);
		static void close(SOCKET fd);

		static int send(SOCKET sockfd, const char* buf, size_t len);
		static int recv(SOCKET sockfd, char* buf, size_t len);
		static int sendto(SOCKET sockfd, const char* buf, size_t len, union_endpoint& ep);
		static int recvfrom(SOCKET sockfd, char* buf, size_t len, union_endpoint& ep);

		static int last_error();
		static bool again();

		static void net_htons(uint16_t h, uint8_t *&n);
		static uint16_t net_ntohs(const uint8_t *n);

		static bool is_valid_ipv4_address(const char *ip);
		static bool is_valid_address(const char *addr); //support ipv4 or dns name
	};
}